package com.yunchang.open.iploaction.parser.cz88;

import com.yunchang.open.iploaction.Location;
import com.yunchang.open.iploaction.parser.IPv4Parser;
import com.yunchang.open.iploaction.utils.IOUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

/**
 * 纯真网cz88.net提供的ipv4免费库的解析类。
 * Created by jasontujun on 2019/6/25.
 *
 * @see <a href="https://www.cz88.net/">纯真网</a>
 */
public class Cz88Parser implements IPv4Parser {

    static final long IP_RECORD_LENGTH = 7;
    // 中国34个省级行政区域（23个省，5个自治区，4个直辖市，2个特别行政区）
    private static final Map<String, String> provinceName = new HashMap<String, String>() {{
        put("北京", "北京");
        put("天津", "天津");
        put("上海", "上海");
        put("重庆", "重庆");
        put("河北", "河北");
        put("山西", "山西");
        put("辽宁", "辽宁");
        put("吉林", "吉林");
        put("黑龙", "黑龙江");
        put("江苏", "江苏");
        put("浙江", "浙江");
        put("安徽", "安徽");
        put("福建", "福建");
        put("江西", "江西");
        put("山东", "山东");
        put("河南", "河南");
        put("湖北", "湖北");
        put("湖南", "湖南");
        put("广东", "广东");
        put("海南", "海南");
        put("四川", "四川");
        put("贵州", "贵州");
        put("云南", "云南");
        put("陕西", "陕西");
        put("甘肃", "甘肃");
        put("青海", "青海");
        put("台湾", "台湾");
        put("内蒙", "内蒙古");
        put("广西", "广西");
        put("西藏", "西藏");
        put("宁夏", "宁夏");
        put("新疆", "新疆");
        put("香港", "香港");
        put("澳门", "澳门");
    }};

    // 4个直辖市
    private static final Map<String, String> municipality = new HashMap<String, String>() {{
        put("北京", "北京");
        put("天津", "天津");
        put("上海", "上海");
        put("重庆", "重庆");
    }};

    // 省名字长度
    private static final Map<String, Integer> provinceLength = new HashMap<String, Integer>() {{
        put("河北", 3);
        put("山西", 3);
        put("辽宁", 3);
        put("吉林", 3);
        put("黑龙", 4);
        put("江苏", 3);
        put("浙江", 3);
        put("安徽", 3);
        put("福建", 3);
        put("江西", 3);
        put("山东", 3);
        put("河南", 3);
        put("湖北", 3);
        put("湖南", 3);
        put("广东", 3);
        put("海南", 3);
        put("四川", 3);
        put("贵州", 3);
        put("云南", 3);
        put("陕西", 3);
        put("甘肃", 3);
        put("青海", 3);
        put("台湾", 3);
        put("内蒙", 3);
        put("广西", 2);
        put("西藏", 2);
        put("宁夏", 2);
        put("新疆", 2);
        put("香港", 2);
        put("澳门", 2);
    }};
    private static final byte REDIRECT_MODE_1 = 0x01;
    private static final byte REDIRECT_MODE_2 = 0x02;
    private static final Logger logger = LoggerFactory.getLogger(Cz88Parser.class);
    private byte[] data;
    private long firstIndexOffset;
    private long totalIndexCount;

    @Override
    public IPv4Parser init(InputStream inputStream) throws Exception {
        data = IOUtil.read(inputStream);
        firstIndexOffset = read4ByteAsLong(0);
        long lastIndexOffset = read4ByteAsLong(4);
        totalIndexCount = (lastIndexOffset - firstIndexOffset) / IP_RECORD_LENGTH + 1;
        return this;
    }

    @Override
    public int getPriority() {
        return DEFAULT_PRIORITY + 1;
    }

    @Override
    public Location findIpv4Location(String ipv4) {
        if (ipv4 == null) {
            return null;
        }
        //“.”号分隔的字符串转换为long类型的数字
        String[] arr = ipv4.split("\\.");
        long numericIp = (Long.parseLong(arr[0]) & 0xFFL) << 24 & 0xFF000000L;
        numericIp |= (Long.parseLong(arr[1]) & 0xFFL) << 16 & 0xFF0000L;
        numericIp |= (Long.parseLong(arr[2]) & 0xFFL) << 8 & 0xFF00L;
        numericIp |= (Long.parseLong(arr[3]) & 0xFFL);
        try {
            int offset = (int) search(numericIp);
            if (offset == -1) {
                return null;
            }
            String area;
            String isp;
            byte redirectMode = data[offset + 4];
            if (redirectMode == REDIRECT_MODE_1) {
                long countryOffset = read3ByteAsLong(offset + 5);
                redirectMode = data[(int) countryOffset];
                if (redirectMode == REDIRECT_MODE_2) {
                    final QQwryString country = readString((int) read3ByteAsLong((int) countryOffset + 1));
                    area = country.string;
                    countryOffset = countryOffset + 4;
                } else {
                    final QQwryString country = readString((int) countryOffset);
                    area = country.string;
                    countryOffset += country.byteCountWithEnd;
                }
                isp = readArea((int) countryOffset);
            } else if (redirectMode == REDIRECT_MODE_2) {
                area = readString((int) read3ByteAsLong(offset + 5)).string;
                isp = readArea(offset + 8);
            } else {
                final QQwryString country = readString(offset + 4);
                area = country.string;
                isp = readArea(offset + 4 + country.byteCountWithEnd);
            }
            return processLocation(area, isp);
        } catch (Exception e) {
            logger.error("", e);
            return null;
        }
    }

    private long search(long ip) {
        long low = 0;
        long high = totalIndexCount;
        long mid = 0;
        while (low <= high) {
            mid = (low + high) >>> 1;
            long indexIP = read4ByteAsLong((int) (firstIndexOffset + (mid - 1) * IP_RECORD_LENGTH));
            long indexIPNext = read4ByteAsLong((int) (firstIndexOffset + mid * IP_RECORD_LENGTH));
            if (indexIP <= ip && ip < indexIPNext) {
                return read3ByteAsLong((int) (firstIndexOffset + (mid - 1) * IP_RECORD_LENGTH + 4));
            } else {
                if (ip > indexIP) {
                    low = mid + 1;
                } else if (ip < indexIP) {
                    high = mid - 1;
                }
            }
        }
        return -1;
    }

    private long read4ByteAsLong(final int offset) {
        long val = data[offset] & 0xFF;
        val |= (data[offset + 1] << 8L) & 0xFF00L;
        val |= (data[offset + 2] << 16L) & 0xFF0000L;
        val |= (data[offset + 3] << 24L) & 0xFF000000L;
        return val;
    }

    private long read3ByteAsLong(final int offset) {
        long val = data[offset] & 0xFF;
        val |= (data[offset + 1] << 8) & 0xFF00;
        val |= (data[offset + 2] << 16) & 0xFF0000;
        return val;
    }

    private String readArea(final int offset) {
        byte redirectMode = data[offset];
        if (redirectMode == REDIRECT_MODE_1 || redirectMode == REDIRECT_MODE_2) {
            long areaOffset = read3ByteAsLong((int) offset + 1);
            if (areaOffset == 0) {
                return "";
            } else {
                return readString((int) areaOffset).string;
            }
        } else {
            return readString(offset).string;
        }
    }

    private QQwryString readString(int offset) {
        int pos = offset;
        final byte[] b = new byte[128];
        int i;
        for (i = 0, b[i] = data[pos++]; b[i] != 0; b[++i] = data[pos++]) ;
        try {
            return new QQwryString(new String(b, 0, i, "GBK"), i + 1);
        } catch (UnsupportedEncodingException e) {
            return new QQwryString("", 0);
        }
    }

    /**
     * 将ip转换为{国家}{省}{市}的格式
     *
     * @param area 地区
     * @param isp  网络供应商
     * @return 返回Location
     */
    private Location processLocation(String area, String isp) {
        if (area == null || area.isEmpty()) {
            return null;
        }
        // 设置isp
        Location location = new Location();
        if (isp != null && !isp.contains("CZ88")) {
            location.setIsp(isp);
        }
        // 设置国家
        if (area.startsWith("中国")) {
            location.setCountry("中国");
        }
        // 过滤掉特殊ip地址结果
        if (area.contains("本机地址") || area.contains("局域网") ||
                area.contains("IANA") || area.contains("骨干网")) {
            return location;
        }
        if (area.length() <= 1) {
            // 地区结果太短，异常地址
            return location;
        }
        String provincePrefix2 = area.substring(0, 2);
        // 如果是中国正常省市的地址，设置省、市
        if (provinceName.containsKey(provincePrefix2)) {
            location.setCountry("中国");
            location.setProvince(provinceName.get(provincePrefix2));
            if (municipality.containsKey(provincePrefix2)) {
                // 处理直辖市的city，直接指定即可
                location.setCity(municipality.get(provincePrefix2));
            } else if (provinceLength.containsKey(provincePrefix2)) {
                // 处理非直辖市的city，需要截取市
                int length = provinceLength.get(provincePrefix2);
                String city = area.length() >= length ? area.substring(length) : null;
                if (city != null && !city.isEmpty()) {
                    if (city.endsWith("市")) {
                        // 去掉结尾的“市”
                        city = city.substring(0, city.length() - 1);
                    }
                    location.setCity(city);
                }
            }
        }
        // 其他就是非中国地区，只能精确到国家
        else {
            location.setCountry(area);
        }
        return location;
    }

    private class QQwryString {

        public final String string;

        public final int byteCountWithEnd;

        public QQwryString(final String string, final int byteCountWithEnd) {
            this.string = string;
            this.byteCountWithEnd = byteCountWithEnd;
        }

        @Override
        public String toString() {
            return string;
        }

    }

}
